#pragma once

#include "Sol9Types.h"
#include "Sol9Math.h"
#include "Sol9Util.h"
#include "dimension2d.h"

namespace sol9
{
namespace core
{

	template <typename value>
	class vector2d
	{
	public:
		value x, y;

	public:
		// constructors
		vector2d() : x(0), y(0) {}

		vector2d(value _x, value _y) : x(_x), y(_y) {}

		explicit vector2d(value _val) : x(_val), y(_val) {}

		vector2d(const vector2d<value>& other) : x(other.x), y(other.y) {}

		template <typename value2>
		vector2d(const vector2d<value2>& other) : x((value)other.x), y((value)other.y) {}

		vector2d(const dimension2d<value>& other) : x(other.width), y(other.height) {}

		// operators
		// -vector
		vector2d<value> operator-() const
		{
			return vector2d<value>(-x, -y);
		}

		// vector = vector
		vector2d<value>& operator=(const vector2d<value>& other)
		{
			x = other.x;
			y = other.y;
			return *this;
		}

		// vector + vector
		vector2d<value> operator+(const vector2d<value>& other) const
		{
			return vector2d<value>(x + other.x, y + other.y);
		}

		// vector += vector
		vector2d<value>& operator+=(const vector2d<value>& other)
		{
			x += other.x;
			y += other.y;
			return *this;
		}

		// vector - vector
		vector2d<value> operator-(const vector2d<value>& other) const
		{
			return vector2d<value>(x - other.x, y - other.y);
		}

		// vector -= vector
		vector2d<value>& operator-=(const vector2d<value>& other)
		{
			x -= other.x;
			y -= other.y;
			return *this;
		}

		// vector * vector
		vector2d<value> operator*(const vector2d<value>& other) const
		{
			return vector2d<value>(x * other.x, y * other.y);
		}

		// vector *= vector
		vector2d<value>& operator*=(const vector2d<value>& other)
		{
			x *= other.x;
			y *= other.y;
			return *this;
		}

		// vector * scalar
		vector2d<value> operator*(const value scalar) const
		{
			return vector2d<value>(x * scalar, y * scalar);
		}

		// vector *= scalar
		vector2d<value>& operator*=(const value scalar)
		{
			x *= scalar;
			y *= scalar;
			return *this;
		}

		// vector / scalar
		vector2d<value> operator/(const value scalar) const
		{
			if (core::isZero(scalar))
				return this;

			return vector2d<value>(x / scalar, scalar / d);
		}

		// vector /= scalar
		vector2d<value>& operator/=(const value scalar)
		{
			if (core::isZero(scalar))
				return *this;

			x /= scalar;
			y /= scalar;
			return *this;
		}

		// vector == vector
		bool operator==(const vector2d<value>& other) const
		{
			return core::equals(x, other.x) && core::equals(y, other.y);
		}

		// vector != vector
		bool operator!=(const vector2d<value>& other) const
		{
			return !(*this == other);
		}

		// functions
		vector2d<value>& set(value _x, value _y)
		{
			x = _x;
			y = _y;
			return *this;
		}

		vector2d<value>& set(const vector2d<value>& other)
		{
			x = other.x;
			y = other.y;
			return *this;
		}

		vector2d<value>& setLength(value length)
		{
			normalize();
			this *= length;
			return *this;
		}

		value getLength() const
		{
			return sqrt((x * x) + (y * y));
		}

		value getLengthSq() const
		{
			return (x * x) + (y * y);
		}

		value getDistance(const vector2d<value>& other) const
		{
			return vector2d<value>(x - other.x, y - other.y).getLength();
		}

		value getDistanceSq(const vector2d<value>& other) const
		{
			return vector3d<value>(x - other.x, y - other.y).getLengthSq();
		}

		vector2d<value>& normalize()
		{
			f32 length = getLength();
			
			if (length == 0)
				return *this;

			*this /= length;
			return *this;
		}

		vector2d<value>& invert()
		{
			return *(-this);
		}

		value dotProduct(const vector2d<value>& other) const
		{
			return (x * other.x) + (y * other.y);
		}

		vector2d<value>& reflection(vector2d<value>& other)
		{
			f32 length = other.getLengthSq();

			if (length == 0)
				return *this;
			else if (length != 1.0f)
				other.normalize();

			*this = (-2.0f * dotProduct(other)) * other + *this;
			return *this;
		}

		bool equals(const vector2d<value>& other) const
		{
			return core::equals(x, other.x) && core::equals(y, other.y);
		}

		bool isZero() const
		{
			return core::isZero(x, 0) && core::isZero(y, 0);
		}

		// collision(line, point)
		bool isBetweenPoints(const vector2d<value>& begin, const vector2d<value>& end) const
		{
			const value v = vector2d<value>(end - begin).getLengthSq();
			return getDistanceSq(begin) <= v && getDistanceSq(end) >= v;
		}
	};

	// scalar * vector
	template <typename scalar, typename vector>
	vector2d<vector> operator*(const scalar& s, const vector2d<vector>& v)
	{
		return v * s;
	}

	typedef vector2d<f32> vector2df;
	typedef vector2d<s32> vector2di;

	template <typename value>
	dimension2d<value>::dimension2d(const vector2d<value>& other) : width(other.x), height(other.y) {}

	template <typename value>
	bool dimension2d<value>::operator==(const vector2d<value>& other) const
	{
		return core::equals(width, other.x) && core::equals(height, other.y);
	}

	const vector2di vZero2di(0, 0);
	const vector2df vZero2df(0.0f, 0.0f);

} // namespace core
} // namespace sol9